package com.study.example.study.extension.easyexcel.write;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Stopwatch;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * easyexcel 写数据策略工厂
 *
 * @author RenYuLiang
 */
@Log4j2
@Component
public class EasyExcelWriteDataStrategyFactory {

    /**
     * 日期格式
     */
    private static final String DATA_FORMAT = "yyyy-MM-dd-HH-mm-ss";

    private static final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
            new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

    /**
     * 写入 文件
     *
     * @param strategyEnum EasyExcelWriteStrategyEnum 策略类型
     * @param arg          Object 参数
     */
    public void write(final EasyExcelWriteStrategyEnum strategyEnum, final Object arg) {
        // 建的时候就开始计时
        Stopwatch stopwatch = Stopwatch.createStarted();
        if (Objects.isNull(strategyEnum)) {
            return;
        }
        final String path = "D:\\easyexcel\\";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATA_FORMAT);
        final String nowTime = formatter.format(LocalDateTime.now());
        String fileName = path + strategyEnum.getFileName() + "_" + nowTime + ".xlsx";
        // 这里 写到不同的 sheet 不同的对象
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
            // 写数据
            this.writeData(strategyEnum, excelWriter, arg);
        }
        // 停止计时，然后计算时长.单位为毫秒
        long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        log.info("写excel导出结束,耗时(毫秒)={}", elapsed);
        System.out.println("写excel导出结束,耗时(毫秒)=" + elapsed);
    }

    /**
     * 下载 文件 并且失败的时候返回json
     *
     * @param response HttpServletResponse
     * @param type     String 策略类型
     * @param arg      Object 参数
     */
    public void download(final HttpServletResponse response, final String type, final Object arg) throws IOException {
        // 建的时候就开始计时
        Stopwatch stopwatch = Stopwatch.createStarted();
        EasyExcelWriteStrategyEnum strategyEnum = EasyExcelWriteStrategyEnum.getByType(type);
        if (Objects.isNull(strategyEnum)) {
            return;
        }
        // 这里 写到不同的 sheet 不同的对象
        ExcelWriter excelWriter = null;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATA_FORMAT);
        final String nowTime = formatter.format(LocalDateTime.now());
        // 文件名
        String fileName = strategyEnum.getFileName() + "_" + nowTime + ".xlsx";
        try {
            // 这里注意 有同学反应使用 swagger 会导致各种问题，请直接用浏览器或者用 postman
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");

            // 这里 URLEncoder.encode 可以防止中文乱码 当然和 easyexcel 没有关系
            fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

            excelWriter = EasyExcel.write(response.getOutputStream()).build();
            // 写数据
            this.writeData(strategyEnum, excelWriter, arg);
        } catch (Exception e) {
            log.error("写excel发生异常", e);
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        } finally {
            if (Objects.nonNull(excelWriter)) {
                excelWriter.finish();
            }
        }
        // 停止计时，然后计算时长.单位为毫秒
        long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        log.info("写excel导出结束,耗时(毫秒)={}", elapsed);
    }

    /**
     * 写数据
     *
     * @param strategyEnum 写数据策略
     * @param excelWriter  ExcelWriter
     * @param org          Object
     */
    private void writeData(final EasyExcelWriteStrategyEnum strategyEnum, final ExcelWriter excelWriter, final Object org) {
        // 单线程 多个 sheet 循环写数据
//        for (int i = 0; i < strategyEnum.getSheetArray().length; i++) {
//            Stopwatch stopwatch = Stopwatch.createStarted();
//            this.writeSheet(strategyEnum, excelWriter, i, org);
//            // 停止计时，然后计算时长.单位为毫秒
//            long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
//            System.out.println(i + ":写excel数据,耗时(毫秒)=" + elapsed);
//        }
        // 多线程 多个 sheet 循环写数据
        int sheetThreadCount = strategyEnum.getSheetArray().length;
        CountDownLatch countDownLatch = new CountDownLatch(sheetThreadCount);
        for (int j = 0; j < sheetThreadCount; j++) {
            int finalJ = j;
            executorService.execute(() -> {
                Stopwatch stopwatch = Stopwatch.createStarted();
                try {
                    this.writeSheet(strategyEnum, excelWriter, finalJ, org);
                } catch (Exception e) {
                    log.error("多线程错误={}", e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
                // 停止计时，然后计算时长.单位为毫秒
                long elapsed = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
                System.out.println(finalJ + ":写excel数据,耗时(毫秒)=" + elapsed);
            });

        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            log.error("多线程错误={}", e.getMessage(), e);
        } finally {
            executorService.shutdown();
        }
    }

    /**
     * 单个 sheet 写数据
     *
     * @param strategyEnum EasyExcelWriteStrategyEnum
     * @param excelWriter  ExcelWriter
     * @param i            int 第几个sheet
     * @param arg          参数
     */
    private void writeSheet(final EasyExcelWriteStrategyEnum strategyEnum, ExcelWriter excelWriter, int i, Object arg) {
        String sheetName = "sheet";
        if (StringUtils.isBlank(strategyEnum.getWriteHandlerArray()[i])) {
            return;
        }
        // 获取 sheet 写数据的策略
        EasyExcelWriteDataStrategy<?> strategy = SpringUtil.getBean(strategyEnum.getWriteHandlerArray()[i]);
        if (Objects.isNull(strategy)) {
            return;
        }
        // 每次都要创建 writeSheet 这里注意必须指定 sheetNo 而且 sheetName 必须不一样
        if (StringUtils.isNotBlank(strategyEnum.getSheetArray()[i])) {
            sheetName = strategyEnum.getSheetArray()[i];
        } else {
            sheetName = sheetName + i;
        }
        EasyExcelWriteDataDTO<?> easyExcelWriteDataDTO = strategy.listWriteData(arg);
        // 这里注意  需要指定写用哪个 class 去写
        WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).head(strategy.getWriteClass()).build();
        // 分页去数据库查询数据
        excelWriter.write(easyExcelWriteDataDTO.getList(), writeSheet);
    }

}
